Akka.NET 으로 만드는 온라인 게임 서버
지난 NDC 2016 에서 발표했던 “Akka.NET 으로 만드는 온라인 게임 서버” 의 발표 슬라이드와 발표 때 있었던 질답을 여기에 정리해 둡니다.
발표자료
Q&A
기본 사용
# 아카닷넷에서 멀티스레드 동기화와 스레드 블락킹 이슈를 신경써야만 하는지 궁금합니다.
기본적으로는 아닙니다. Actor Model 은 멀티스레드 동기화와 스레드 블로킹 이슈와 같은 사용하기 어려운 개념 없이 많은 액터가 각자의 메시지를 동시에 처리할 수 있도록 해주는 모델입니다.
# 아카닷넷에서 기타 액터 상태 액세스는 메시지 파싱으로만 되나요? 그렇다면 레이턴시와IO의 오버헤드를 겪지 않으셨나요?
네 메시지 패싱으로만 됩니다. 레이턴시와 IO 오버헤드가 있지만 잘 최적화 되어 사용할 수 있었습니다.
# 부모 액터가 죽으면 자식 액터들은 어떻게 반응 하게 되나요? SPOF가 되진 않나요?
부모 액터가 죽으면 해당 부모의 모든 자식 액터는 소멸됩니다. 액터 수준에서는 SPOF 를 해결하기 어려우며 보통은 Cluster 과 같은 상위 도구를 사용해서 해결합니다.
# 한 액터의 메시지 큐에 메시지가 많이 쌓일 경우 이게 액터의 퍼포먼스에도 영향을 주나요?
액터의 메시지 큐의 경우 단순한 큐라 쌓인 만큼 메모리를 소모하며 해당 액터는 동시에 하나의 메시지만 처리 가능하기 때문에 해당 액터의 반응성이 떨어질 수 있습니다. 다만 다른 액터의 성능에는 영향을 주지 않습니다.
# 같은 actor를 여러 클러스터에서 참조할 경우 데이터 동기화는 어떻게 하는지 궁금합니다.별도로 lock을 하나요?
Actor 는 ActorRef 를 통해 메시지를 보낼 뿐이므로 여러 노드에서 같은 Actor 를 참조하더라도 데이터를 직접 변경할 수 없습니다. 여러 노드에서 하나의 Actor 로 ActorRef 를 통해 메시지를 보내면 해당 Actor 는 순차적으로 메시지를 처리하기 때문에 lock 이 필요하지 않습니다.
# 아카에서 한 액터가 여러 액터에 같은 메시지를 동시에 뿌릴 수 있는(broadcast)같은 기능이 있나요?
네 있습니다. 와일드카드가 포함된 ActorSelection 을 사용하거나 Broadcast Router 를 사용할 수 있습니다.
프로그래밍 패턴
# 액터들끼리 서로 메세지를 보내고 응답을 await하고 있으면 데드락이 발생할 수 있을 것 같은데, 어떻게 대응하면 좋을까요?
두 액터가 서로에게 메시지를 보내고 await 상태에 들어가면 데드락이 발생할 수 있습니다. 일반적으로 Akka 에서는 여러 액터가 서로 메시지를 보내도 응답을 기다리는 패턴을 권장하지 않고 있습니다. 하지만 필요하다면 다음과 같은 우회 방법이 있습니다.
-
일반적인 여러 객체가 Lock 을 거는 시나리오와 유사하게 Actor 간 await 계층을 두는 방법으로 우회할 수 있습니다. 락 계층에 대해서는 Use Lock Hierarchies to Avoid Deadlock 을 참고하세요.
-
Akka.Interfaced 의 ReentrantAttribute 를 사용하면 해당 메소드가 await 상태가 들어가더라도 다른 메시지를 핸들링 할 수 있습니다. 이런 경우 데드락을 피할 수 있으나 해당 async 핸들러가 처리 중에 다른 메시지가 처리될 수 있음을 가정하고 작성해야 하는 수고가 생깁니다.
# 액터 모델로 MMORPG도 만들 수 있을까요? MMORPG는 여러 액터들이 상호작용하는 경우가 많은데 유저 하나하나를 액터로 만들면 상호작용을 만들기 어려울 것 같아서요.
액터간 상호작용이 동기적이고 상태 의존이 많다면 액터의 매시지 패싱만으로는 로직을 구현하기 어렵습니다. 따라서 이를 좀 더 단순하게 만드는 장치가 필요한데 TicTacToe 의 경우에는 이러한 역할을 하는 것이 GameActor 입니다. 모든 유저의 요청은 모두 GameActor 의 하나의 액터가 처리하기 되어 있으며 이런 경우 모든 상태 변경이 싱글 스레드로 처리할 수 있어 크게 어렵지 않게 로직을 담을 수 있습니다. World 가 서로 분리된 Zone 으로 구성된 MMORPG 의 경우에도 유사한 접근을 사용할 수 있을 것 같습니다. 하지만 Zone 으로 구분되지 않은 완전히 열린 월드를 액터를 통해 구축하는 것은 도전적인 과제로 보입니다.
# 실시간 게임에서는 shared state가 일반적인데 actor model 에서는 어떻게 풀 수 있을까요. 예를 들면, 두 플레이어(액터)각각 아이템의 집합을 가지고 있고 이를 특정 조건 하에서만 (다른 플레이어의 상태에 의존성이 있으며 두 액터의 조건이 동시 만족해야 하는 경우) 교환할 수 있는 트랜잭션 조건을 액터 모델에서는 어떻게 접근하면 좋을까요
만약 Zone state 와 같이 하나의 액터에 상태를 몰아 놓을 수 있다면 쉽게 transaction 을 처리할 수 있습니다. 하지만 유저의 인벤토리와 같이 그렇게 처리하기 어려운 경우에는 보통의 분산 트랜잭션을 사용하는 것이 일반적입니다. Two-Phase commit protocol 이 일반적이며 How do I do transactions across a distributed system? 를 참고하면 좋을 것 같습니다.
# TrackableData에서 SET에 대해서는 공유 데이터에서 동시성 이슈가 있을 것 같은데 어떤식으로 처리하시나요?
TrackableData 는 하나의 데이터에 대해 동시에 여러곳에서의 변경을 처리하기 위한 것이 아닙니다. 한 곳에서의 변경을 여러곳에 전파하기 위한 것입니다. 따라서 보통 Actor 의 데이터 역할을 하며 (예를 들면 유저 Actor 가 유저 데이터를 변경하듯) Actor 의 메시지 처리가 순차적으로 진행되기 때문에 동시성 문제를 겪지 않습니다.
연동
# C#이 아닌 언어와의 연동에 대한 방법도 제시 가능한가요?
일반적인 .NET 언어 C#, F#, VB.NET 모두 사용할 수 있습니다. 다만 .NET 을 벗어나는 언어는 아직 가능하지 않습니다.
# 아카로된 프로젝트의 테스트 코드를 짤 때 더 편하거나 어려운 점이 있나요?
액터의 매시지 패싱이 일반적인 함수 호출과 달리 덜 결정적이라 기본적으로 테스트 코드를 작성하기 어렵습니다. 하지만 Akka.NET 은 이를 위한 테스트 헬퍼를 제공해 큰 어려움 없이 테스트 코드를 작성할 수 있습니다. 좀 더 자세한 내용은 How to Unit Test Akka.NET Actors with Akka.TestKit 를 참고하세요.
# 스칼라 akka와 닷넷 akka 와 rpc 바인딩이 되나요?
아쉽게도 현재 Akka.NET 은 JVM Akka 와 통신을 할 수 없습니다. 참고.
성능
# 퍼포먼스 이슈가 제법 나올 것 같은데 부하테스트 등을 간단히 해보신 적이 있으신지요?
Akka 는 어느정도 최적화가 되어 있습니다. Akka.NET 문서에 따르면 대략 1대 물리 머신에서 초당 5천만 메시지를 처리할 수 있고 1GB 당 2.5백만 액터를 메모리에 적재할 수 있습니다.
# 액터모델 성능 어떤가요? 서버 수백대 상황에서 대부분의 메시지가 네트워크 통해 전달되면 딜레이가 느껴지지 않을까요?
서버가 수백대인 상황에서는 일반적인 서버 프로그래밍과 같이 messaging locality 를 신경써야 합니다. 원격 메시징 보다 로컬 메시징이 훨씬 싸기 때문에 상호작용이 많은 액터를 최대한 같은 물리 머신 내에 두는 전략이 필요합니다.
기타
# 돌격전차 개발환경이 C# IOCP라고 하셨는데 C#.NET에서는 IOCP API가 제공되지 않는 것으로 알고있습니다. C#.NET에서 IOCP API를 어떻게 연결하셨나요?
.NET 의 기본 Socket 은 IOCP 로 구현되어 있어 IOCP 라고 한 것입니다. 직접 Windows Native IOCP API 를 사용하지는 않았습니다.
# Full GC레이턴시때문에 문제생긴적 있으세요? 없다면 혹시 실서비스 최대 Heap 사이즈 여쭤봐도 될까요?
Full GC 문제는 .NET 의 일반적인 문제이나 돌격전차는 해당 문제를 겪지 않았습니다. GC 부하를 줄이기 위해 빈번한 메모리 할당 해제를 줄여 두기도 했었고 게임 자체가 기만한 서버의 반응을 요구하는 게임이 아니었기 때문입니다. 최대 Heap 크기는 정확하게 기억하지 않으나 1~2GB 안팎이이었던 것 같습니다.